﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using Lidgren;
using Lidgren.Network;

namespace Networking
{
    public enum LogLevel
    {
        None,
        All
    }

    public enum Headers // == The packet type which includes the player's key
    {
        // Signal == Which only sends signals and no extra data and works like a transmission
        PlayerSyncData,
        PlayerNewData,
        Signal
    }

    public enum SignalType // SubHeader
    {
        PlayerLeft // Signal
    }

    public enum SyncDataType // SubHeader
    {
        Position,
        Rotation,
        Animation
    }

    /*
     * Signal works like:
     * Packet 1: Player Key.
     * Packet 2: Signal(Header).
     * Packet 3: Signal Type(Subheader).
    */

    /*
     * Player Sync Data works like:
     * Packet 1: Player Key.
     * Packet 2: PlayerSyncData(Header).
     * Packet 3: SyncDataType(Subheader).
     * Packet 4 and higher: Data Values.
    */

    /*
     * Player New Data works like:
     * Packet 1: Player Key.
     * Packet 2: PlayerNewData(Header).
     * Packet 3: Player Count.
     * Packet 4 and higher: Player Data Properties per player.
    */

    public class Server
    {
        public LogLevel Log { get; set; }
        public NetServer NetServer;
        public NetPeerConfiguration Config;
        public delegate void ClientConnectEventHandler(int playerkey, NetIncomingMessage incmsg);
        public delegate void ClientDisconnectEventHandler(int playerkey, string reason, NetIncomingMessage incmsg);
        public delegate void ReceivePlayerSyncDataEventHandler(int playerkey, SyncDataType syncdatatype, NetIncomingMessage incmsg);
        public delegate void ReceiveSignalEventHandler(int playerkey, SignalType signaltype, NetIncomingMessage incmsg);
        public event ClientConnectEventHandler OnClientConnect;
        public event ClientDisconnectEventHandler OnClientDisconnect;
        public event ReceivePlayerSyncDataEventHandler OnReceivePlayerSyncData;
        public event ReceiveSignalEventHandler OnReceiveSignal;
        public Dictionary<int, Player> Players;

        public Server(int port, LogLevel log)
        {
            Log = log;

            try
            {
                if (Log != LogLevel.None)
                {
                    Config = new NetPeerConfiguration("MyGame");
                    Message("Peer configuration initialized.");
                    Config.Port = port;
                    Message("Port set to " + port.ToString() + ".");
                    NetServer = new NetServer(Config);
                    Message("Server initialized.");
                    Players = new Dictionary<int, Player>();
                    Message("Players dictionary intialized.");
                }
                else
                {
                    Config = new NetPeerConfiguration("MyGame");
                    Config.Port = port;
                    NetServer = new NetServer(Config);
                    Players = new Dictionary<int, Player>();
                }
            }
            catch (Exception ex)
            {
                if (Log != LogLevel.None) Message(ex.ToString());
            }
        }

        public void Start()
        {
            NetServer.Start();
        }

        public void Stop()
        {
            NetServer.Shutdown("Bye");
        }

        public Player GetPlayerFromKey(int key)
        {
            foreach (KeyValuePair<int, Player> player in Players)
                if (player.Key == key) return player.Value;

            return null;
        }

        public Player GetPlayerFromCon(NetConnection con)
        {
            foreach (KeyValuePair<int, Player> player in Players)
                if (player.Value.Connection == con) return player.Value;

            return null;
        }

        public int GenerateUniqueKey()
        {
            int key = 5000;
            Random rnd = new Random();

            do
            {
                key = rnd.Next(5000, 25000);
            }
            while (KeyTaken(key));

            return key;
        }

        public bool KeyTaken(int key)
        {
            foreach (KeyValuePair<int, Player> player in Players)
                if (player.Key == key) return true;

            return false;
        }

        #region Logs
        public void Message(string text)
        {
            Console.WriteLine("SERVER: " + text);
        }
        public void MsgFrom(string msg, NetConnection con)
        {
            Console.WriteLine("SERVER: " + msg + " from ({0})", con.ToString());
        }
        public void MsgTo(string msg, NetConnection con)
        {
            Console.WriteLine("SERVER: " + msg + " to ({0})", con.ToString());
        }
        #endregion

        public void SendSignal(NetConnection con, SignalType signaltype, int fromkey = 0)
        {
            NetOutgoingMessage outmsg = NetServer.CreateMessage();
            outmsg.Write(fromkey);
            outmsg.Write((byte)Headers.Signal);
            outmsg.Write((byte)signaltype);
            NetServer.SendMessage(outmsg, con, NetDeliveryMethod.ReliableSequenced);
            if (Log != LogLevel.None) MsgTo("Sending signal", con);
        }

        public void SendSignalToAll(SignalType signaltype, int fromkey = 0, NetConnection except = null)
        {
            NetOutgoingMessage outmsg = NetServer.CreateMessage();
            outmsg.Write(fromkey);
            outmsg.Write((byte)Headers.Signal);
            outmsg.Write((byte)signaltype);

            if (except == null)
            {
                NetServer.SendToAll(outmsg, NetDeliveryMethod.ReliableSequenced);
                if (Log != LogLevel.None) Message("Sending signal to all");
            }
            else
            {
                NetServer.SendToAll(outmsg, except, NetDeliveryMethod.ReliableSequenced, 0);
                if (Log != LogLevel.None) Message("Sending signal to all except (" + except.ToString() + ").");
            }
        }

        public void SendPlayerDataToNewPlayer(NetConnection con, Player player)
        {
            NetOutgoingMessage outmsg = NetServer.CreateMessage();
            outmsg.Write(player.Key);
            outmsg.Write((byte)Headers.PlayerNewData);
            outmsg.Write(Players.Count);

            if (Players.Count <= 1)
            {
                outmsg.WriteAllProperties(player);
                NetServer.SendMessage(outmsg, con, NetDeliveryMethod.ReliableSequenced);
                if (Log != LogLevel.None) MsgTo("Sending player data", con);
            }
            else
            {
                foreach (KeyValuePair<int, Player> allplayer in Players) outmsg.WriteAllProperties(allplayer.Value);
                NetServer.SendMessage(outmsg, con, NetDeliveryMethod.ReliableSequenced);
                if (Log != LogLevel.None) MsgTo("Sending all player data", con);
            }
        }

        public void SendNewPlayerDataToAll(Player player, NetConnection except)
        {
            NetOutgoingMessage outmsg = NetServer.CreateMessage();
            outmsg.Write(player.Key);
            outmsg.Write((byte)Headers.PlayerNewData);
            outmsg.Write(Players.Count);
            outmsg.WriteAllProperties(player);
            NetServer.SendToAll(outmsg, except, NetDeliveryMethod.ReliableSequenced, 0);
            if (Log != LogLevel.None) Message("Sending new player data to all");
        }

        public NetOutgoingMessage BeginSendPlayerDataToAll(int playerkey, SyncDataType syncdatatype)
        {
            NetOutgoingMessage outmsg = NetServer.CreateMessage();
            outmsg.Write(playerkey);
            outmsg.Write((byte)Headers.PlayerSyncData);
            outmsg.Write((byte)syncdatatype);

            return outmsg;
        }

        public void EndSendPlayerDataToAll(NetOutgoingMessage outmsg, NetConnection except = null)
        {
            if (except == null)
            {
                NetServer.SendToAll(outmsg, NetDeliveryMethod.ReliableSequenced);
                if (Log != LogLevel.None) Message("Sending player data to all clients.");
            }
            else
            {
                NetServer.SendToAll(outmsg, except, NetDeliveryMethod.ReliableSequenced, 0);
                if (Log != LogLevel.None) Message("Sending player data to all clients except ( " + except.ToString() + ").");
            }
        }

        public Player CreatePlayer(NetIncomingMessage incmsg)
        {
            int playerkey = GenerateUniqueKey();
            Player newplayer = new Player();
            newplayer.Key = playerkey;
            newplayer.Name = "Player " + playerkey.ToString();
            newplayer.PosX = 60.0f;
            newplayer.PosY = 60.0f;
            newplayer.Connection = incmsg.SenderConnection;

            return newplayer;
        }

        public void NetUpdate()
        {
            NetIncomingMessage incmsg;

            if ((incmsg = NetServer.ReadMessage()) != null)
            {
                int playerkey;

                switch (incmsg.MessageType)
                {
                    case NetIncomingMessageType.StatusChanged:

                        NetConnectionStatus status = (NetConnectionStatus)incmsg.ReadByte();
                        string reason = incmsg.ReadString();

                        if (status == NetConnectionStatus.Connected)
                        {
                            Player newplayer = CreatePlayer(incmsg);
                            Players.Add(newplayer.Key, newplayer);
                            SendPlayerDataToNewPlayer(newplayer.Connection, newplayer);
                            SendNewPlayerDataToAll(newplayer, newplayer.Connection);
                            if (OnClientConnect != null) OnClientConnect(newplayer.Key, incmsg);
                        }
                        
                        if (status == NetConnectionStatus.Disconnected)
                        {
                            playerkey = GetPlayerFromCon(incmsg.SenderConnection).Key;
                            if (OnClientDisconnect != null) OnClientDisconnect(playerkey, reason, incmsg);
                            Players.Remove(playerkey);
                        }

                        break;

                    case NetIncomingMessageType.Data:

                        playerkey = incmsg.ReadInt32();
                        Headers header = (Headers)incmsg.ReadByte();

                        switch (header)
                        {
                            case Headers.PlayerSyncData:

                                SyncDataType syncdatatype = (SyncDataType)incmsg.ReadByte();
                                if (OnReceivePlayerSyncData != null) OnReceivePlayerSyncData(playerkey, syncdatatype, incmsg);  
                                break;

                            case Headers.Signal:

                                SignalType signaltype = (SignalType)incmsg.ReadByte();
                                if (OnReceiveSignal != null) OnReceiveSignal(playerkey, signaltype, incmsg);
                                break;
                        }
                        
                        break;
                }
            }
        }
    }
}
